#include <arpa/inet.h>
#include <stdint.h>
#include <err.h>
#include "apns_manager.h"
#include <apn.h>
#include <version.h>
#include <boost/algorithm/string.hpp>
#include "PushService_constants.h"

namespace imserver {

int DEVICE_BINARY_SIZE = 32;
int MAXPAYLOAD_SIZE = 256;

void init_OpenSSL(void) {
  if (!SSL_library_init()) {
    LOG_ERROR("init_OpenSSL --> SSL_library_init failed.");
    exit(-1);
  }
  SSL_load_error_strings();
}

unsigned int htod(char c) {
  unsigned int i;
  if (c >= 'a' && c <= 'f')
    i = c - 'a' + 10;
  else if (c >= 'A' && c <= 'F')
    i = c - 'A' + 10;
  else if (c >= '0' && c <= '9')
    i = c - '0';
  else {
    throw wrong_token_string("wrong char of token");
  }
  return i;
}

unsigned int char2binary(const std::string& s) {
  unsigned int dec = 0;
  for (int i = 0; s[i] != 0; ++i) {
    try {
      dec = dec * 16 + htod(s[i]);
    } catch (const wrong_token_string &e) {
      throw;
    }
  }
  return dec;
}

std::vector<unsigned> string2binary(std::string s) {
  std::string::iterator itr;
  std::vector<unsigned> binary;
  for (itr = s.begin(); itr != s.end(); ++itr) {
    if (*itr == ' ')
      s.erase(itr);
  }
  if(s.size() != 64)
    throw wrong_token_string("wrong size of token");

  int i;

  for (itr = s.begin(), i = 0; i < 8; itr += 8, ++i) {
    std::string tmpstring(itr, itr + 8);
    try {
      binary.push_back(char2binary(tmpstring));
    } catch (const wrong_token_string &e) {
      throw;
    }
  }
  return binary;
}

void revert(unsigned int in[]) {
  int i;
  for (i = 0; i < 8; i++) {
    in[i] = htonl(in[i]);
  }
}

void APNSPusher::run() {
  apache::thrift::concurrency::Synchronized s(_monitor);

  while (1) {
    while (_msgQueue.size() == 0 || _ssl_state == false) {
      _monitor.wait();
    }

    PushMsgEntry m = _msgQueue.front();
    _msgQueue.pop_front();

    pushOneMsg(m);
  }
}

void APNSPusher::insertMsg(PushMsgEntry & entry) {
  if(entry.payload.size() > 249){
    LOG_ERROR("APNSPusher::insertMsg --> payload exceeds 250 bytes" );
    return;
  }
  apache::thrift::concurrency::Synchronized s(_monitor);
  _msgQueue.push_back(entry);
  _monitor.notify();
}

int APNSPusher::_sendPayload(SSL *sslPtr, const std::vector<unsigned> & TokenVec,
    const char *payloadBuff, size_t payloadLength) {
  int rtn = 0;
  unsigned deviceTokenBinary[8];

  memcpy(deviceTokenBinary, &TokenVec[0], sizeof(int) * TokenVec.size());
  revert(deviceTokenBinary);

  if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength) {
    uint8_t command = 0; /* command number */

    char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint16_t)
     + DEVICE_BINARY_SIZE + sizeof(uint16_t) + payloadLength];
    //char binaryMessageBuff[293];
    /* message format is, |COMMAND|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD| */
    char *binaryMessagePt = binaryMessageBuff;
    uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE);
    uint16_t networkOrderPayloadLength = htons(payloadLength);
    /* command */
    *binaryMessagePt++ = command;
    /* token length network order */
    memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t));
    binaryMessagePt += sizeof(uint16_t);
    /* device token */
    memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE);
    binaryMessagePt += DEVICE_BINARY_SIZE;
    /* payload length network order */
    memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t));
    binaryMessagePt += sizeof(uint16_t);
    /* payload */
    memcpy(binaryMessagePt, payloadBuff, payloadLength);
    binaryMessagePt += payloadLength;

    int code =SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt  - binaryMessageBuff));
    if (code >= 0) {
      rtn = 1;
      LOG_INFO("APNSPusher::_sendPayload --> SSL_write() Success");
    } else {
      LOG_ERROR("APNSPusher::_sendPayload --> SSL_write() fail");
    }

    switch(SSL_get_error(sslPtr, code)) {
    case SSL_ERROR_NONE:
      LOG_ERROR("APNSPusher::_sendPayload --> SSL_ERROR_NONE");
      break;
    case SSL_ERROR_ZERO_RETURN:
      LOG_ERROR("APNSPusher::_sendPayload --> SSL_ERROR_ZERO_RETURN" );
      break;
    case SSL_ERROR_WANT_READ:
      LOG_ERROR("APNSPusher::_sendPayload --> SSL_ERROR_WANT_READ" );
      break;
    case SSL_ERROR_WANT_WRITE:
      LOG_ERROR("APNSPusher::_sendPayload --> SSL_ERROR_WANT_WRITE");
      break;
    case SSL_ERROR_SYSCALL:
      LOG_ERROR("APNSPusher::_sendPayload --> SSL_ERROR_SYSCALL");
      break;
    default:
      //LOG_INFO("APNSPusher::_sendPayload --> SSL_get_error --> In default");
      break;
    }
  }
  return rtn;
}

APNSPusher::~APNSPusher() {
  SSL_clear(_ssl);
  LOG_INFO("SSL Connection closed\n");

  SSL_free(_ssl);
  SSL_CTX_free(_ctx);

}
bool APNSPusher::_init_connection(){
  init_OpenSSL();

  _ctx = _setup_client_ctx();

  if(!_ctx){
    return false;
  }
  while(!(_conn = BIO_new_connect(SERVER  ":"  PORT) )){
    LOG_ERROR("APNSPusher::_init_connection --> Error creating connection BIO");
  }

  while(BIO_do_connect(_conn) <= 0){
    LOG_ERROR("APNSPusher::_init_connection --> Error connecting to remote machine");
    sleep(2);
  }

  while(!(_ssl = SSL_new(_ctx))){
    LOG_ERROR("APNSPusher::_init_connection --> Error creating an SSL context");
    sleep(2);
  }
  SSL_set_bio(_ssl, _conn, _conn);

  while(SSL_connect(_ssl) <= 0){
    LOG_ERROR( "Error connecting SSL object");;
    sleep(2);
  }

  LOG_INFO("APNSPusher::_init_connection --> SSL Connection opened!\n");
  _ssl_state = true;
  return true;
}

bool APNSPusher::init() {
  if(_init_connection()){
    return true;
  }
  return false;
}

void APNSPusher::pushOneMsg(PushMsgEntry & m) {
  std::vector<unsigned int> token;
  try {
    token = string2binary(m.tokenStr);
    while (_sendPayload(_ssl, token, m.payload.c_str(), m.payload.size()) == 0) {
      _ssl_state = false;
      LOG_ERROR("APNSPusher::pushOneMsg --> send fail,reconnecting...");
      SSL_clear(_ssl);
      LOG_ERROR("APNSPusher::pushOneMsg --> SSL Connection closed");
      SSL_free(_ssl);
      _init_connection();

    }
  } catch (const wrong_token_string &e) {
    LOG_ERROR("APNSPusher::pushOneMsg --> error:" << e.what());
    return;
  }
}

SSL_CTX * APNSPusher::_setup_client_ctx(void) {
  SSL_CTX *ctx;

  ctx = SSL_CTX_new(SSLv23_method());

  if (SSL_CTX_use_certificate_chain_file(ctx, _certfile.c_str()) != 1){
    LOG_ERROR("APNSPusher::_setup_client_ctx --> Error loading certificate from file");
  }
  SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *)const_cast<char *>(_password.c_str()) );

  if (SSL_CTX_use_PrivateKey_file(ctx, _certfile.c_str(), SSL_FILETYPE_PEM) != 1){
    LOG_ERROR("APNSPusher::_setup_client_ctx --> Error loading private key from file");
  }
  return ctx;
}

void APNSFeedback::run() {
  while (1) {
    sleep(24*3600);

    apn_ctx_ref ctx = NULL;
    apn_error_ref error;
    char **tokens = NULL;
    uint32_t tokens_count = 0;
    uint32_t i = 0;
    const char *cert_path = _certfile.c_str();
    const char *key_path = _certfile.c_str();
    const char *key_passwd = _password.c_str();
    if(apn_init(&ctx, cert_path, key_path, key_passwd, &error) == APN_ERROR){
      LOG_ERROR("APNSFeedback::run --> error:" << error->message << " errno:" << APN_ERR_CODE_WITHOUT_CLASS(error->code));
      apn_error_free(&error);
      continue;
    }
    #ifdef APNS_SANDBOX
    apn_set_mode(ctx, APN_MODE_SANDBOX, NULL);
    #else
    apn_set_mode(ctx, APN_MODE_PRODUCTION, NULL);
    #endif
    if(apn_feedback_connect(ctx, &error) == APN_ERROR) {
      LOG_ERROR("APNSFeedback::run --> Could not connected to Apple Feedback Servece. error:" << error->message <<
          " errno:" << APN_ERR_CODE_WITHOUT_CLASS(error->code));
      apn_free(&ctx);
      apn_error_free(&error);
      continue;
    }
    if(apn_feedback(ctx, &tokens, &tokens_count, &error) == APN_ERROR) {
      LOG_ERROR("APNSFeedback::run --> Could not get tokens. error:" << error->message << "errno:" <<
          APN_ERR_CODE_WITHOUT_CLASS(error->code));
      apn_close(ctx);
      apn_free(&ctx);
      apn_error_free(&error);
      continue;
    }
    LOG_INFO("APNSFeedback::run --> get count:" << tokens_count);
    for(i = 0; i < tokens_count; i++) {
      LOG_DEBUG("APNSFeedback::run --> token:" << tokens[i]);
    }
    apn_feedback_tokens_array_free(tokens, tokens_count);
    apn_close(ctx);
    apn_free(&ctx);
  }
}

const uint32_t APNSManager::PUSHER_SIZE = 1;

void APNSManager::pushMsg(int64_t uid, const std::string& token, const std::string& content) {
  if (token.empty() || token.find(g_PushService_constants.APNS_TOKEN_PREFIX) != 0) {
    LOG_ERROR("APNSManager::pushMsg --> token error. uid:" << uid << " token:" << token);
  }
  PushMsgEntry msgEntry;
  msgEntry.uid = uid;
  msgEntry.tokenStr = token.substr(token.rfind(":"));
  std::string body(content);
  boost::algorithm::replace_all(body, "\\", "\\\\");
  boost::algorithm::replace_all(body, "\"", "\\\"");
  std::ostringstream os;
  os << "{\"aps\":{\"alert\":\"" << body << "\"}}";
  msgEntry.payload = os.str();
  (_pusher[uid % PUSHER_SIZE])->insertMsg(msgEntry);
}

}
